use actix_web::Result;
use validator::{Validate, ValidationErrors};
use serde_json::json;

use crate::error::AppError;

pub fn validate_request<T: Validate>(data: &T) -> Result<(), AppError> {
    match data.validate() {
        Ok(_) => Ok(()),
        Err(errors) => {
            let error_messages = format_validation_errors(&errors);
            Err(AppError::validation(error_messages))
        }
    }
}

fn format_validation_errors(errors: &ValidationErrors) -> String {
    let mut messages = Vec::new();
    
    for (field, field_errors) in errors.field_errors() {
        for error in field_errors {
            let message = error.message
                .as_ref()
                .map(|m| m.to_string())
                .unwrap_or_else(|| format!("Invalid value for field '{}'.", field));
            messages.push(format!("{}: {}", field, message));
        }
    }
    
    messages.join(", ")
}

pub fn validate_uuid(uuid_str: &str) -> Result<uuid::Uuid, AppError> {
    uuid::Uuid::parse_str(uuid_str)
        .map_err(|_| AppError::bad_request("Invalid UUID format"))
}

pub fn validate_email(email: &str) -> bool {
    use regex::Regex;
    let email_regex = Regex::new(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").unwrap();
    email_regex.is_match(email)
}

pub fn validate_password_strength(password: &str) -> Result<(), AppError> {
    if password.len() < 8 {
        return Err(AppError::validation("Password must be at least 8 characters long"));
    }
    
    if !password.chars().any(|c| c.is_ascii_lowercase()) {
        return Err(AppError::validation("Password must contain at least one lowercase letter"));
    }
    
    if !password.chars().any(|c| c.is_ascii_uppercase()) {
        return Err(AppError::validation("Password must contain at least one uppercase letter"));
    }
    
    if !password.chars().any(|c| c.is_ascii_digit()) {
        return Err(AppError::validation("Password must contain at least one digit"));
    }
    
    if !password.chars().any(|c| "!@#$%^&*()_+-=[]{}|;:,.<>?".contains(c)) {
        return Err(AppError::validation("Password must contain at least one special character"));
    }
    
    Ok(())
}

pub fn validate_username(username: &str) -> Result<(), AppError> {
    if username.len() < 3 {
        return Err(AppError::validation("Username must be at least 3 characters long"));
    }
    
    if username.len() > 50 {
        return Err(AppError::validation("Username must be no more than 50 characters long"));
    }
    
    if !username.chars().all(|c| c.is_alphanumeric() || c == '_' || c == '-') {
        return Err(AppError::validation("Username can only contain letters, numbers, underscores, and hyphens"));
    }
    
    if username.starts_with('_') || username.starts_with('-') {
        return Err(AppError::validation("Username cannot start with underscore or hyphen"));
    }
    
    Ok(())
}

#[derive(serde::Serialize)]
pub struct ValidationErrorResponse {
    pub error: String,
    pub message: String,
    pub details: serde_json::Value,
}

pub fn create_validation_error_response(errors: &ValidationErrors) -> ValidationErrorResponse {
    let mut details = json!({});
    
    for (field, field_errors) in errors.field_errors() {
        let messages: Vec<String> = field_errors
            .iter()
            .map(|error| {
                error.message
                    .as_ref()
                    .map(|m| m.to_string())
                    .unwrap_or_else(|| "Invalid value".to_string())
            })
            .collect();
        
        details[&field.to_string()] = json!(messages);
    }
    
    ValidationErrorResponse {
        error: "VALIDATION_ERROR".to_string(),
        message: "Request validation failed".to_string(),
        details,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use validator::Validate;
    use serde::Deserialize;

    #[derive(Deserialize, Validate)]
    struct TestStruct {
        #[validate(email)]
        email: String,
        #[validate(length(min = 3, max = 50))]
        username: String,
    }

    #[test]
    fn test_validate_request_success() {
        let data = TestStruct {
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
        };
        
        assert!(validate_request(&data).is_ok());
    }

    #[test]
    fn test_validate_request_failure() {
        let data = TestStruct {
            email: "invalid-email".to_string(),
            username: "ab".to_string(), // Too short
        };
        
        assert!(validate_request(&data).is_err());
    }

    #[test]
    fn test_validate_email() {
        assert!(validate_email("test@example.com"));
        assert!(validate_email("user.name+tag@domain.co.uk"));
        assert!(!validate_email("invalid-email"));
        assert!(!validate_email("@domain.com"));
        assert!(!validate_email("user@"));
    }

    #[test]
    fn test_validate_password_strength() {
        assert!(validate_password_strength("Password123!").is_ok());
        assert!(validate_password_strength("short").is_err());
        assert!(validate_password_strength("nouppercase123!").is_err());
        assert!(validate_password_strength("NOLOWERCASE123!").is_err());
        assert!(validate_password_strength("NoNumbers!").is_err());
        assert!(validate_password_strength("NoSpecialChars123").is_err());
    }

    #[test]
    fn test_validate_username() {
        assert!(validate_username("validuser").is_ok());
        assert!(validate_username("user_name").is_ok());
        assert!(validate_username("user-name").is_ok());
        assert!(validate_username("user123").is_ok());
        
        assert!(validate_username("ab").is_err()); // Too short
        assert!(validate_username("_username").is_err()); // Starts with underscore
        assert!(validate_username("-username").is_err()); // Starts with hyphen
        assert!(validate_username("user@name").is_err()); // Invalid character
    }

    #[test]
    fn test_validate_uuid() {
        let valid_uuid = "550e8400-e29b-41d4-a716-446655440000";
        let invalid_uuid = "not-a-uuid";
        
        assert!(validate_uuid(valid_uuid).is_ok());
        assert!(validate_uuid(invalid_uuid).is_err());
    }
}